package nl.ejvos.bgpgames.cbbchess.client;

import java.util.ArrayList;
import java.util.Iterator;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class Board extends SimplePanel
{
	public ChessClient chessClient;

	// Private constructor for Boards. To create one, use the static
	// CreateBoard method
	public Board(Element element, ChessClient chessClient)
	{
		super(element);
		this.chessClient = chessClient;
		m_selectedMovelist = new ArrayList<Square>();
		m_result = ONGOING;
		m_nranks = 8;
		m_nfiles = 8;
		m_color = Piece.WHITE;
		m_turn = 0;
		m_startPly = -1;
		m_loading = true;
		m_selectedPiece = null;
		m_lastMovedPiece = null;
		m_userid = null;
		m_postmoveURL = GWT.getModuleBaseURL() + "postmove.php";
		m_canPlay = new boolean[2];
		m_canPlay[Piece.WHITE] = m_canPlay[Piece.BLACK] = false;
		m_canPotentiallyCastle = new int[2][2];
		m_canPotentiallyCastle[0] = new int[2];
		m_canPotentiallyCastle[1] = new int[2];
		m_canPotentiallyCastle[0][0] = -1;
		m_canPotentiallyCastle[0][1] = -1;
		m_canPotentiallyCastle[1][0] = -1;
		m_canPotentiallyCastle[1][1] = -1;
		m_canACastle = m_canHCastle = false;
		m_isCurrent = true;

		// Elements corresponding to following strings
		Element useridInput = null, posthashInput = null, optionFlagsInput = null, intervalInput = null;
		// Get the (up to four) input children of the div, if they exist.
		int childCount = DOM.getChildCount(element);
		DOM.setInnerText(element, "Loading..");
		// In case the DIV was set invisible
		Element l = DOM.createElement("link");
		l.setAttribute("rel", "stylesheet");
		l.setAttribute("type", "text/css");
		l.setAttribute("href", "/resources/cbbchess/themes/wikipedia-48/ChessByBlog.css");
	RootPanel.getBodyElement().getParentElement().getFirstChildElement().appendChild(l);
		setVisible(true);

		// Deferring the final board creation allows the "Loading"
		// text to be seen
		DeferredCommand.addCommand(new Command()
		{
			public void execute()
			{
				try
				{
					Board.this.FinishCreate();
				}
				catch (Exception e)
				{
					DOM.setInnerText(Board.this.getElement(), e.toString());
				}
			}
		});
		onAttach();
	}

	public void FinishCreate()
	{
		// Create the user interface elements
		final VerticalPanel vpanel = new VerticalPanel();

		// Hide the vpanel until it finishes laying out
		DOM.setStyleAttribute(vpanel.getElement(), "visibility", "hidden");
		m_whiteLabel = new Label();
		m_blackLabel = new Label();
		m_whiteLabel.setStyleName("CBB-side-label");
		m_blackLabel.setStyleName("CBB-side-label");
		final TightGrid boardPanel = new TightGrid();
		m_grid = new TightGrid("CBB-chessboard");
		int nrows = 4; // for at least m_blackLabel, m_grid, m_whiteLabel,
		// m_statusLbel
		if (m_eventLabel != null)
			nrows++;
		if (m_siteLabel != null)
			nrows++;
		if (m_topFileLabelGrid != null)
			nrows++;
		if (m_bottomFileLabelGrid != null)
			nrows++;
		boardPanel.resize(nrows, 3);
		nrows = 0;
		if (m_eventLabel != null)
		{
			boardPanel.setWidget(nrows++, 1, m_eventLabel);
		}
		if (m_siteLabel != null)
		{
			boardPanel.setWidget(nrows++, 1, m_siteLabel);
		}
		boardPanel.setWidget(nrows++, 1, m_blackLabel);
		if (m_topFileLabelGrid != null)
		{
			boardPanel.setWidget(nrows++, 1, m_topFileLabelGrid);
		}

		if (m_leftRankLabelGrid != null)
		{
			boardPanel.setWidget(nrows, 0, m_leftRankLabelGrid);
		}
		boardPanel.setWidget(nrows, 1, m_grid);
		if (m_rightRankLabelGrid != null)
		{
			boardPanel.setWidget(nrows, 2, m_rightRankLabelGrid);
		}
		nrows++;
		if (m_bottomFileLabelGrid != null)
		{
			boardPanel.setWidget(nrows++, 1, m_bottomFileLabelGrid);
		}
		boardPanel.setWidget(nrows++, 1, m_whiteLabel);
		m_statusLabel = new Label();
		m_statusLabel.setStyleName("CBB-status");
		boardPanel.setWidget(nrows++, 1, m_statusLabel);

		final TightGrid buttonPanel = new TightGrid();
		buttonPanel.resize(1, 10);
		m_aCastleButton = new SizedPushButton("", "CBB-acastle-button", "A-Side Castle", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				if (m_canACastle)
				{
					int rank = m_kings[m_color].getSquare().getRank() - 1;
					m_kings[m_color].castle("O-O-O", m_squares[m_canPotentiallyCastle[m_color][0]][rank].getPiece(), m_squares[2][rank], m_squares[3][rank]);
				}
			}
		});
		m_hCastleButton = new SizedPushButton("", "CBB-hcastle-button", Character.toString((char) ('A' + m_nfiles - 1)) + "-Side Castle", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				if (m_canHCastle)
				{
					int rank = m_kings[m_color].getSquare().getRank() - 1;
					m_kings[m_color].castle("O-O", m_squares[m_canPotentiallyCastle[m_color][1]][rank].getPiece(), m_squares[m_nfiles - 2][rank], m_squares[m_nfiles - 3][rank]);
				}
			}
		});
		m_drawButton = new SizedPushButton("", "CBB-draw-button", "Offer Draw", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				if (Board.this.m_result == ONGOING)
				{
					new OfferDrawDialog(Board.this).show();
				}
				else
				{
					new AcceptDrawDialog(Board.this).show();
				}
			}
		});
		m_resignButton = new SizedPushButton("", "CBB-resign-button", "Resign", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				new ResignDialog(Board.this).show();
			}
		});
		m_flipButton = new SizedPushButton("", "CBB-flip-button", "Flip Board", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Board.this.flip();
			}
		});
		m_startButton = new SizedPushButton("", "CBB-start-button", "Rewind To Start", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Board.this.m_gameLogTable.start();
			}
		});
		m_rewindButton = new SizedPushButton("", "CBB-rewind-button", "Rewind", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Board.this.m_gameLogTable.rewind();
			}
		});
		m_forwardButton = new SizedPushButton("", "CBB-forward-button", "Forward", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Board.this.m_gameLogTable.forward();
			}
		});
		m_endButton = new SizedPushButton("", "CBB-end-button", "Forward To End", new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				Board.this.m_gameLogTable.end();
			}
		});

		buttonPanel.setWidget(0, 0, m_hCastleButton);
		buttonPanel.setWidget(0, 1, m_aCastleButton);
		buttonPanel.setWidget(0, 2, m_drawButton);
		buttonPanel.setWidget(0, 3, m_resignButton);
		buttonPanel.setWidget(0, 4, m_flipButton);
		buttonPanel.setWidget(0, 5, m_startButton);
		buttonPanel.setWidget(0, 6, m_rewindButton);
		buttonPanel.setWidget(0, 7, m_forwardButton);
		buttonPanel.setWidget(0, 8, m_endButton);
		vpanel.add(buttonPanel);
		vpanel.add(boardPanel);

		// DEBUG
		// FIXME
		// TODO
		// HACK
		// by ejvos
		m_canPlay[Piece.WHITE] = true;
		m_canPlay[Piece.BLACK] = true;
		if (m_eventLabel != null)
		{
			if (m_eventString != null)
			{
				if (m_roundString != null)
				{
					m_eventLabel.setText(m_eventString + ", Round " + m_roundString);
				}
				else
				{
					m_eventLabel.setText(m_eventString);
				}
			}
		}
		if (m_siteLabel != null)
		{
			if (m_siteString != null)
			{
				if (m_dateString != null)
				{
					m_siteLabel.setText(m_siteString + ", " + m_dateString);
				}
				else
				{
					m_siteLabel.setText(m_siteString);
				}
			}
			else if (m_dateString != null)
			{
				m_siteLabel.setText(m_dateString);
			}
		}
		if (m_whiteLastName != null && !m_whiteLastName.equals(""))
		{
			m_whiteLabel.setText(m_whiteLastName + "/White");
		}
		else
		{
			m_whiteLabel.setText("White");
		}
		if (m_blackLastName != null && !m_blackLastName.equals(""))
		{
			m_blackLabel.setText(m_blackLastName + "/Black");
		}
		else
		{
			m_blackLabel.setText("Black");
		}

		DOM.setElementProperty(m_hCastleButton.getElement(), "title", Character.toString((char) ('A' + m_nfiles - 1)) + "-Side Castle");
		// Set up labels
		if (m_leftRankLabelGrid != null)
		{
			m_leftRankLabelGrid.resize(m_nranks, 1);
		}
		if (m_rightRankLabelGrid != null)
		{
			m_rightRankLabelGrid.resize(m_nranks, 1);
		}
		for (int rank = 0; rank < m_nranks; ++rank)
		{
			if (m_leftRankLabelGrid != null)
			{
				m_leftRankLabelGrid.setText(rank, 0, Integer.toString(m_nranks - rank));
			}
			if (m_rightRankLabelGrid != null)
			{
				m_rightRankLabelGrid.setText(rank, 0, Integer.toString(m_nranks - rank));
			}
		}
		// The file labels are padded by one cell because of the left rank label
		if (m_topFileLabelGrid != null)
		{
			m_topFileLabelGrid.resize(1, m_nfiles + 1);
		}
		if (m_bottomFileLabelGrid != null)
		{
			m_bottomFileLabelGrid.resize(1, m_nfiles + 1);
		}
		for (char file = 0; file < m_nfiles; ++file)
		{
			if (m_topFileLabelGrid != null)
			{
				m_topFileLabelGrid.setText(0, file + 1, Character.toString((char) (file + 'a')));
			}
			if (m_bottomFileLabelGrid != null)
			{
				m_bottomFileLabelGrid.setText(0, file + 1, Character.toString((char) (file + 'a')));
			}
		}

		// Set up the array of squares
		m_squares = new Square[m_nfiles][m_nranks];
		int squareColor = Piece.BLACK;
		m_grid.resize(m_nranks, m_nfiles);
		for (char file = 0; file < m_nfiles; ++file)
		{
			m_squares[file] = new Square[m_nranks];
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				Square square = new Square((char) (file + 'a'), rank + 1, squareColor, this);
				m_squares[file][rank] = square;
				m_grid.setWidget(m_nranks - rank - 1, file, square);
				squareColor = 1 - squareColor;
			}
			squareColor = 1 - squareColor;
		}
		for (char file = 0; file < m_nfiles; ++file)
		{
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				if (file != m_nfiles - 1)
				{
					m_squares[file][rank].setRight(m_squares[file + 1][rank]);
				}
				if (file != 0)
				{
					m_squares[file][rank].setLeft(m_squares[file - 1][rank]);
				}
				if (rank != m_nranks - 1)
				{
					m_squares[file][rank].setTop(m_squares[file][rank + 1]);
				}
				if (rank != 0)
				{
					m_squares[file][rank].setBottom(m_squares[file][rank - 1]);
				}
			}
		}

		// set up initial positions
		parseFenString("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");

		m_gameLogTable = new GameLogTable(this, m_whiteLastName, m_blackLastName, getMap());
		m_logPanel = new DisclosurePanel("Initial position");
		m_logPanel.setOpen(true);
		m_logPanel.setStyleName("CBB-gamelogDisclosurePanel");
		m_logPanel.add(m_gameLogTable);
		m_logPanel.getHeader().setStyleName("CBB-header");
		// DEBUG
		// HACK
		// TODO
		// FIXME by ejvos tmp disabled
		// vpanel.add(m_logPanel);

		// This is some serious skullduggery to deal with the fact
		// that the DisclosurePanel class is final, and contains an
		// anchor tag child that by default has the class "header" -
		// which conflicts with many WordPress style sheets. Said
		// child can't be altered in any way, since it's not the
		// header widget, it wraps the header widget.
		DOM.setElementProperty(DOM.getChild(DOM.getChild(DOM.getChild(DOM.getChild(m_logPanel.getElement(), 0), 0), 0), 0), "className", "CBB-header");
		// More skullduggery to deal with the child that has the style
		// "content"
		DOM.setElementProperty(DOM.getChild(DOM.getChild(DOM.getChild(DOM.getChild(DOM.getChild(m_logPanel.getElement(), 0), 1), 0), 0), 0), "className", "CBB-content");

		// Advance to starting position
		if (m_startPly != -1)
		{
			m_gameLogTable.setPly(m_startPly);
		}
		m_loading = false;
		DOM.setInnerText(getElement(), "");
		add(vpanel);

		// Last command to execute: sets the Square sizes correctly,
		// after that's done we can go ahead and make the board visible
		DeferredCommand.addCommand(new Command()
		{
			public void execute()
			{
				for (int rank = 0; rank < m_nranks; ++rank)
				{
					for (int file = 0; file < m_nfiles; ++file)
					{
						m_squares[file][rank].fixWidth();
					}
				}

				m_hCastleButton.fixWidth();
				m_aCastleButton.fixWidth();
				m_drawButton.fixWidth();
				m_resignButton.fixWidth();
				m_flipButton.fixWidth();
				m_startButton.fixWidth();
				m_rewindButton.fixWidth();
				m_forwardButton.fixWidth();
				m_endButton.fixWidth();

				// Resize the labels to match the computed square sizes
				for (int rank = 0; rank < m_nranks; ++rank)
				{
					if (m_leftRankLabelGrid != null)
					{
						m_leftRankLabelGrid.getCellFormatter().setHeight(rank, 0, DOM.getElementProperty(m_grid.getCellFormatter().getElement(rank, 0), "clientHeight"));
					}
					if (m_rightRankLabelGrid != null)
					{
						m_rightRankLabelGrid.getCellFormatter().setHeight(rank, 0, DOM.getElementProperty(m_grid.getCellFormatter().getElement(rank, 0), "clientHeight"));
					}
				}
				for (int file = 0; file < m_nfiles; ++file)
				{
					if (m_topFileLabelGrid != null)
					{
						m_topFileLabelGrid.getCellFormatter().setWidth(0, file + 1, DOM.getElementProperty(m_grid.getCellFormatter().getElement(0, file), "clientWidth"));
					}
					if (m_bottomFileLabelGrid != null)
					{
						m_bottomFileLabelGrid.getCellFormatter().setWidth(0, file + 1, DOM.getElementProperty(m_grid.getCellFormatter().getElement(0, file), "clientWidth"));
					}
				}

				// With the board properly laid out, the spinner can
				// be initialized with the correct position
				m_spinner = new SpinPanel(Board.this);

				// Finally, we can make the board visible!
				DOM.setStyleAttribute(vpanel.getElement(), "visibility", "visible");
			}
		});
	}

	public void parseFenString(String fenString)
	{
		for (int rank = 0; rank < m_nranks; ++rank)
		{
			for (int file = 0; file < m_nfiles; ++file)
			{
				m_squares[file][rank].setPiece(null);
			}
		}

		String[] fenTokens = fenString.split(" ");
		if (fenTokens.length == 6)
		{
			// Set up pieces
			m_allPieces = new ArrayList<ArrayList<Piece>>(2);
			m_allPieces.add(new ArrayList<Piece>());
			m_allPieces.add(new ArrayList<Piece>());
			m_kings = new King[2];
			String[] fenRanks = fenTokens[0].split("/");
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				int file = 0, letter = 0;
				while (letter < fenRanks[m_nranks - 1 - rank].length() && file < m_nfiles)
				{
					char symbol = fenRanks[m_nranks - 1 - rank].charAt(letter);
					if (Character.isDigit(symbol))
					{
						switch (symbol)
						{
							case '2':
							case '3':
							case '4':
							case '5':
							case '6':
							case '7':
							case '8':
							case '9':
								file += symbol - '0';
								break;
							case '1':
								// Special case: if the next character is a 0,
								// increment by 10. Otherwise increment by 1.
								if (letter + 1 < fenRanks[m_nranks - 1 - rank].length() && fenRanks[m_nranks - 1 - rank].charAt(letter + 1) == '0')
								{
									file += 10;
								}
								else
								{
									file++;
								}
								break;
							default:
								break;
						}
					}
					else
					{
						Piece p = Piece.createPieceFromSymbol(symbol, m_squares[file][rank]);
						if (p != null)
						{
							m_allPieces.get(p.getColor()).add(p);
							if (p instanceof King)
							{
								m_kings[p.getColor()] = (King) p;
							}
						}
						file++;
					}
					letter++;
				}
			}

			// Parse the turn first, then set up active color
			m_turn = Integer.parseInt(fenTokens[5]) - 1;
			if (fenTokens[1].startsWith("w"))
			{
				m_color = Piece.WHITE;
			}
			else
			{
				updateGameLogTable("...");
				m_color = Piece.BLACK;
			}

			// Check en passant target square
			if (!fenTokens[3].startsWith("-") && fenTokens[3].length() >= 2)
			{
				int file = fenTokens[3].charAt(0) - 'a';
				int rank = fenTokens[3].charAt(1) - '1';
				// look for the pawn that made the move, which is
				// ahead of this square, and indicate that it can be
				// captured
				if (m_color == Piece.BLACK && rank == 2 && file >= 0 && file < m_nfiles)
				{
					Piece pawn = m_squares[file][rank + 1].getPiece();
					if (pawn != null && pawn instanceof Pawn && pawn.getColor() == Piece.WHITE)
					{
						setLastMovedPiece(pawn);
						((Pawn) pawn).setEnPassantCapturable(true);
					}
				}
				else if (m_color == Piece.WHITE && rank == (m_nranks - 3) && file >= 0 && file < m_nfiles)
				{
					Piece pawn = m_squares[file][rank - 1].getPiece();
					if (pawn != null && pawn instanceof Pawn && pawn.getColor() == Piece.BLACK)
					{
						setLastMovedPiece(pawn);
						((Pawn) pawn).setEnPassantCapturable(true);
					}
				}
			}

			// Castling availability.
			for (int letter = 0; letter < fenTokens[2].length(); letter++)
			{
				switch (fenTokens[2].charAt(letter))
				{
					case 'K':
						for (int file = m_kings[Piece.WHITE].getSquare().getFile() - 'a' + 1; file < m_nfiles; ++file)
						{
							Piece rook = m_squares[file][m_kings[Piece.WHITE].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.WHITE)
							{
								m_canPotentiallyCastle[Piece.WHITE][1] = file;
								break;
							}
						}
						break;
					case 'Q':
						for (int file = m_kings[Piece.WHITE].getSquare().getFile() - 'a' - 1; file >= 0; --file)
						{
							Piece rook = m_squares[file][m_kings[Piece.WHITE].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.WHITE)
							{
								m_canPotentiallyCastle[Piece.WHITE][0] = file;
								break;
							}
						}
						break;
					case 'k':
						for (int file = m_kings[Piece.BLACK].getSquare().getFile() - 'a'; file < m_nfiles; ++file)
						{
							Piece rook = m_squares[file][m_kings[Piece.BLACK].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.BLACK)
							{
								m_canPotentiallyCastle[Piece.BLACK][1] = file;
								break;
							}
						}
						break;
					case 'q':
						for (int file = m_kings[Piece.BLACK].getSquare().getFile() - 'a' - 1; file >= 0; --file)
						{
							Piece rook = m_squares[file][m_kings[Piece.BLACK].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.BLACK)
							{
								m_canPotentiallyCastle[Piece.BLACK][0] = file;
								break;
							}
						}
						break;
					default:
						// Try to apply Shredder-FEN: encoding the files
						// directly
						if (fenTokens[2].charAt(letter) >= 'A' && fenTokens[2].charAt(letter) <= 'A' + m_nfiles)
						{
							int file = fenTokens[2].charAt(letter) - 'A';
							Piece rook = m_squares[file][m_kings[Piece.WHITE].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.WHITE)
							{
								if (file > m_kings[Piece.WHITE].getSquare().getFile() - 'a')
								{
									m_canPotentiallyCastle[Piece.WHITE][1] = file;
								}
								else
								{
									m_canPotentiallyCastle[Piece.WHITE][0] = file;
								}
							}
						}
						else if (fenTokens[2].charAt(letter) >= 'a' && fenTokens[2].charAt(letter) <= 'z' + m_nfiles)
						{
							int file = fenTokens[2].charAt(letter) - 'a';
							Piece rook = m_squares[file][m_kings[Piece.BLACK].getSquare().getRank() - 1].getPiece();
							if (rook != null && rook instanceof Rook && rook.getColor() == Piece.BLACK)
							{
								if (file > m_kings[Piece.BLACK].getSquare().getFile() - 'a')
								{
									m_canPotentiallyCastle[Piece.BLACK][1] = file;
								}
								else
								{
									m_canPotentiallyCastle[Piece.BLACK][0] = file;
								}
							}
						}
						break;
				}
			}
			// Ensure the files are sorted
			if (m_canPotentiallyCastle[Piece.WHITE][0] > -1 && m_canPotentiallyCastle[Piece.WHITE][1] > -1 && m_canPotentiallyCastle[Piece.WHITE][0] > m_canPotentiallyCastle[Piece.WHITE][1])
			{
				int swap = m_canPotentiallyCastle[Piece.WHITE][0];
				m_canPotentiallyCastle[Piece.WHITE][0] = m_canPotentiallyCastle[Piece.WHITE][1];
				m_canPotentiallyCastle[Piece.WHITE][1] = swap;
			}
			if (m_canPotentiallyCastle[Piece.BLACK][0] > -1 && m_canPotentiallyCastle[Piece.BLACK][1] > -1 && m_canPotentiallyCastle[Piece.BLACK][0] > m_canPotentiallyCastle[Piece.BLACK][1])
			{
				int swap = m_canPotentiallyCastle[Piece.BLACK][0];
				m_canPotentiallyCastle[Piece.BLACK][0] = m_canPotentiallyCastle[Piece.BLACK][1];
				m_canPotentiallyCastle[Piece.BLACK][1] = swap;
			}
			updateCastlingStatus();
		}
	}

	public void parseMoveList(final String movelistString)
	{
		m_loading = true;
		int c = 0;
		final int strlen = movelistString.length();
		do
		{
			// Figure out next token
			String token = null;
			char cc = movelistString.charAt(c);
			while (c < strlen && Character.isSpace((cc = movelistString.charAt(c))))
			{
				c++;
			}
			if (c >= strlen)
				break;
			// Check for comment start
			if (cc == '{')
			{
				int lastc = c++;
				while (c < strlen && (cc = movelistString.charAt(c)) != '}')
				{
					c++;
				}
				if (c != strlen)
					c++;
				token = movelistString.substring(lastc + 1, c - 1);
				token.replaceAll("\\n", " ");
				// Add comments directly to game log table
				if (!token.equals(""))
				{
					m_gameLogTable.addComment(token);
				}
				if (c >= strlen)
					break;
				else
					continue;
			}
			// Treat recursive annotation variations as comments
			else if (cc == '(')
			{
				int lastc = c++;
				int nestLevel = 1;
				while (c < strlen)
				{
					cc = movelistString.charAt(c);
					c++;
					if (cc == ')')
					{
						nestLevel--;
						if (nestLevel == 0)
						{
							break;
						}
					}
					else if (cc == '(')
					{
						nestLevel++;
					}
				}
				token = movelistString.substring(lastc, c);
				token.replaceAll("\\n", " ");
				// Add comments directly to game log table
				if (!token.equals(""))
				{
					m_gameLogTable.addComment(token);
				}
				if (c >= strlen)
					break;
				else
					continue;
			}
			else
			{
				int lastc = c++;
				while (c < strlen && !Character.isSpace((cc = movelistString.charAt(c))))
				{
					c++;
				}
				token = movelistString.substring(lastc, c);
				if (c != strlen)
					c++;
			}

			if (token == null || token.equals(""))
				break;

			// Ignore numeric annotation glyphs
			if (token.startsWith("$"))
				continue;

			// Ignore leading numbers and dots on move, and trailing
			// +, #, ! and ? signs
			token = token.replaceFirst("^\\d*\\.*([^+#!?]+)[+#!?]*$", "$1");
			// if last character is '=', drop it
			if (token.length() > 1 && token.charAt(token.length() - 1) == '=')
			{
				token = token.substring(0, token.length() - 1);
			}
			if (token.equals("Resigns"))
			{
				resign();
			}
			// Handle castling moves
			else if (token.startsWith("O-O-O"))
			{
				int rank = m_kings[m_color].getSquare().getRank() - 1;
				m_kings[m_color].castle("O-O-O", m_squares[m_canPotentiallyCastle[m_color][0]][rank].getPiece(), m_squares[2][rank], m_squares[3][rank]);
			}
			else if (token.startsWith("O-O"))
			{
				int rank = m_kings[m_color].getSquare().getRank() - 1;
				m_kings[m_color].castle("O-O", m_squares[m_canPotentiallyCastle[m_color][1]][rank].getPiece(), m_squares[m_nfiles - 2][rank], m_squares[m_nfiles - 3][rank]);
			}
			else
			{
				if (token.length() >= 2)
				{
					boolean promotion = false;
					char promoteSymbol = '0';
					// Check for pawn promotion
					if (token.length() > 1 && token.charAt(token.length() - 2) == '=')
					{
						promotion = true;
						promoteSymbol = token.charAt(token.length() - 1);
						token = token.substring(0, token.length() - 2);
					}

					// For all subsequent cases, the target square is the
					// last two characters. We can parse them first
					int toFile = token.charAt(token.length() - 2) - 'a';
					int toRank = token.charAt(token.length() - 1) - '1';
					if (toFile < 0 || toFile >= m_nfiles || toRank < 0 || toRank >= m_nranks)
						continue;
					Square toSquare = m_squares[toFile][toRank];
					token = token.substring(0, token.length() - 2);

					// Check for capture
					if (token.length() > 0 && token.charAt(token.length() - 1) == 'x')
					{
						token = token.substring(0, token.length() - 1);
						// bail if there's nothing to capture! (but
						// watch out for en passant capture)
						if (toSquare.getPiece() == null && !(token.length() > 0 && Character.isLowerCase(token.charAt(0)) && (toRank == 2 || toRank == m_nranks - 3)))
							continue;
					}
					if (token.length() > 0)
					{
						// Handle piece move
						if (Character.isUpperCase(token.charAt(0)))
						{
							int fromFile = -1;
							int fromRank = -1;
							if (token.length() == 3)
							{
								// Piece was disambiguated with rank and file
								fromFile = token.charAt(1);
								fromRank = token.charAt(2) - '0';
								if (fromFile < 'a' || fromFile > 'a' + m_nfiles || fromRank < 1 || fromRank > m_nranks)
									continue;
							}
							else if (token.length() == 2)
							{
								// Piece was disambiguated with either rank or
								// file
								if (Character.isDigit(token.charAt(1)))
								{
									fromRank = token.charAt(1) - '0';
									if (fromRank < 1 || fromRank > m_nranks)
										continue;
								}
								else
								{
									fromFile = token.charAt(1);
									if (fromFile < 'a' || fromFile > 'a' + m_nfiles)
										continue;
								}
							}
							if (token.charAt(0) == 'K')
							{
								if (m_kings[m_color].canMoveTo(toSquare))
								{
									m_kings[m_color].move(toSquare);
								}
							}
							else
							{
								for (Piece piece : m_allPieces.get(m_color))
								{
									if (fromRank != -1 && piece.getSquare().getRank() != fromRank)
										continue;
									if (fromFile != -1 && piece.getSquare().getFile() != fromFile)
										continue;
									if (piece.getSymbol() == token.charAt(0))
									{
										if (piece.canMoveTo(toSquare))
										{
											piece.move(toSquare);
											break;
										}
									}
								}
							}
						}
						// Should be pawn capture
						else
						{
							int fromFile = token.charAt(0);
							if (fromFile < 'a' || fromFile > 'a' + m_nfiles)
								continue;
							if (m_color == Piece.WHITE && toRank > 0)
							{
								Piece pawn = m_squares[fromFile - 'a'][toRank - 1].getPiece();
								if (pawn != null)
								{
									if (promotion && pawn instanceof Pawn)
									{
										((Pawn) pawn).promote(toSquare, promoteSymbol);
									}
									else
									{
										pawn.move(toSquare);
									}
								}
								else if (toRank == m_nranks - 3)
								{ // en passant
									pawn = m_squares[fromFile - 'a'][3].getPiece();
									if (pawn != null)
									{
										pawn.move(toSquare);
									}
								}
							}
							else if (m_color == Piece.BLACK && toRank < m_nranks - 1)
							{
								Piece pawn = m_squares[fromFile - 'a'][toRank + 1].getPiece();
								if (pawn != null)
								{
									if (promotion && pawn instanceof Pawn)
									{
										((Pawn) pawn).promote(toSquare, promoteSymbol);
									}
									else
									{
										pawn.move(toSquare);
									}
								}
								else if (toRank == 2)
								{ // en passant
									pawn = m_squares[fromFile - 'a'][4].getPiece();
									if (pawn != null)
									{
										pawn.move(toSquare);
									}
								}
							}
						}
					}
					// Should be pawn move
					else
					{
						if ((m_color == Piece.WHITE && toRank > 1) || (m_color == Piece.BLACK && toRank < m_nranks - 2))
						{
							Piece pawn = m_squares[toFile][toRank + (m_color == Piece.WHITE ? -1 : 1)].getPiece();
							if (pawn != null)
							{
								if (promotion && pawn instanceof Pawn)
								{
									((Pawn) pawn).promote(toSquare, promoteSymbol);
								}
								else
								{
									pawn.move(toSquare);
								}
							}
							else
							{
								pawn = m_squares[toFile][toRank + (m_color == Piece.WHITE ? -2 : 2)].getPiece();
								// Can't promote on a two step move
								if (pawn != null)
								{
									pawn.move(toSquare);
								}
							}
						}
					}
				}
			}
		}
		while (c < strlen);
		m_loading = false;
		updateStatusLabel();
	}

	// Returns the color whose turn it is
	public int getColor()
	{
		return m_color;
	}

	// Returns whether the current color is actually allowed to play
	// based on userids
	public boolean canPlay()
	{
		return (m_result == ONGOING) && (m_canPlay[m_color]);
	}

	// Marks a piece as being selected
	public void selectPiece(Piece piece)
	{
		if (m_selectedPiece == piece)
		{
			m_selectedPiece = null;
			piece.getSquare().setSelected(false);
			setMoveableSquares(false);
		}
		else
		{
			if (m_selectedPiece != null)
			{
				m_selectedPiece.getSquare().setSelected(false);
				setMoveableSquares(false);
			}
			piece.getSquare().setSelected(true);
			m_selectedPiece = piece;
			setMoveableSquares(true);
		}
	}

	// Marks or clears the squares to which the currently selected
	// piece can move
	protected final void setMoveableSquares(boolean moveable)
	{
		if (moveable)
		{
			m_selectedMovelist.clear();
			m_selectedPiece.getMoveList(m_selectedMovelist);
			for (Square movesquare : m_selectedMovelist)
			{
				movesquare.setMoveable(moveable);
			}
		}
		else
		{
			for (Square movesquare : m_selectedMovelist)
			{
				movesquare.setMoveable(moveable);
			}
		}
	}

	public void offerDraw()
	{
		if (!m_loading)
		{
			if (m_color == Piece.WHITE)
			{
				m_updateCommand = new Command()
				{
					public void execute()
					{
						Board.this.m_result = WHITEOFFEREDDRAW;
					}
				};
				m_updateNotation = null;
				doPostTurn(m_postmoveURL, "move=1/2-&postid=" + DOM.getElementProperty(getElement(), "id"));
			}
			else
			{
				m_updateCommand = new Command()
				{
					public void execute()
					{
						Board.this.m_result = BLACKOFFEREDDRAW;
					}
				};
				m_updateNotation = null;
				doPostTurn(m_postmoveURL, "move=-1/2&postid=" + DOM.getElementProperty(getElement(), "id"));
			}
		}
		else
		{
			m_result = (m_color == Piece.WHITE) ? WHITEOFFEREDDRAW : BLACKOFFEREDDRAW;
			updateGameLogTable(null);
		}
	}

	public void acceptDraw()
	{
		if (!m_loading)
		{
			m_updateCommand = new Command()
			{
				public void execute()
				{
					Board.this.m_result = DRAW;
				}
			};
			m_updateNotation = null;
			doPostTurn(m_postmoveURL, "move=1/2 - 1/2&postid=" + DOM.getElementProperty(getElement(), "id"));
		}
		else
		{
			m_result = DRAW;
			updateGameLogTable(null);
		}
	}

	public void refuseDraw()
	{
		if (!m_loading)
		{
			m_updateCommand = new Command()
			{
				public void execute()
				{
					Board.this.m_result = ONGOING;
				}
			};
			m_updateNotation = null;
			doPostTurn(m_postmoveURL, "move=*&postid=" + DOM.getElementProperty(getElement(), "id"));
		}
		else
		{
			m_result = ONGOING;
			updateGameLogTable(null);
		}
	}

	public void resign()
	{
		if (!m_loading)
		{
			String serverNotation;
			if (m_color == Piece.WHITE)
			{
				serverNotation = (m_turn + 1) + ". Resigns";
				m_updateCommand = new Command()
				{
					public void execute()
					{
						Board.this.m_result = BLACKWON;
					}
				};
			}
			else
			{
				serverNotation = " Resigns";
				m_updateCommand = new Command()
				{
					public void execute()
					{
						Board.this.m_result = WHITEWON;
					}
				};
			}
			m_updateNotation = "Resigns";
			doPostTurn(m_postmoveURL, "move=" + serverNotation + "&postid=" + DOM.getElementProperty(getElement(), "id"));
		}
		else
		{
			m_result = (m_color == Piece.WHITE) ? BLACKWON : WHITEWON;
			updateGameLogTable("Resigns");
		}
	}

	// Handles change of turn. Input string is the move notation for
	// the current turn.
	public void nextTurn(String notation, Command updateCommand)
	{
		if (updateCommand != null)
			updateCommand.execute();
		updateGameLogTable(notation);
	}

	private void updateGameLogTable(String notation)
	{
		{
			if (notation != null)
			{
				if (m_color == Piece.WHITE)
				{
					m_turn++;
				}
				if (m_color == Piece.WHITE)
				{
					m_logPanel.getHeaderTextAccessor().setText(Integer.toString(m_turn) + "." + notation);
				}
				else
				{
					m_logPanel.getHeaderTextAccessor().setText(Integer.toString(m_turn) + "... " + notation);
				}
				m_gameLogTable.addMove(m_color, m_turn, notation, getMap());
				m_color = 1 - m_color;
				char lastchar = notation.charAt(notation.length() - 1);
				if (lastchar == '+')
				{
					if (notation.charAt(notation.length() - 2) == '+')
					{
						m_result = (m_color == Piece.WHITE) ? BLACKWON : WHITEWON;
					}
				}
				else if (lastchar == '=')
				{
					m_result = DRAW;
				}
			}
			updateCastlingStatus();
			// Update the status label
			updateStatusLabel();
		}
	}

	private void updateCastlingStatus()
	{
		m_canACastle = m_canHCastle = false;

		if (isChecked(m_color))
			return;

		// Determine whether the color can castle on the a side
		if (m_canPotentiallyCastle[m_color][0] >= 0)
		{
			boolean canCastle = true;
			// Make sure king can move all the way to the c file. Note
			// that this may be to the left or to the right of the
			// current king's file!
			King king = m_kings[m_color];
			Square kingSquare = king.getSquare();
			Square square = kingSquare;
			while (square.getFile() != 'c')
			{
				if (square.getFile() > 'c')
				{
					square = square.getNeighbor(Square.LEFT);
				}
				else
				{
					square = square.getNeighbor(Square.RIGHT);
				}
				if (square.getFile() - 'a' != m_canPotentiallyCastle[m_color][0] && !king.isValidMove(square))
				{
					canCastle = false;
					break;
				}
			}
			if (canCastle)
			{
				// So far, so good. Make sure the rook can move all the
				// way to the d file.
				square = m_squares[m_canPotentiallyCastle[m_color][0]][kingSquare.getRank() - 1];
				Piece rook = square.getPiece();
				if (rook != null && rook instanceof Rook)
				{
					while (square.getFile() != 'd')
					{
						if (square.getFile() > 'd')
						{
							square = square.getNeighbor(Square.LEFT);
						}
						else
						{
							square = square.getNeighbor(Square.RIGHT);
						}
						if (square != kingSquare && square.getPiece() != null)
						{
							canCastle = false;
							break;
						}
					}
				}
			}
			m_canACastle = canCastle;
		}
		// Determine whether the color can castle on the h side
		if (m_canPotentiallyCastle[m_color][1] >= 0)
		{
			boolean canCastle = true;
			// Make sure king can move all the way to the g file. Note
			// that this may be to the left or to the right of the
			// current king's file!
			King king = m_kings[m_color];
			Square kingSquare = king.getSquare();
			Square square = kingSquare;
			while (square.getFile() != 'a' + m_nranks - 2)
			{
				if (square.getFile() > 'a' + m_nranks - 2)
				{
					square = square.getNeighbor(Square.LEFT);
				}
				else
				{
					square = square.getNeighbor(Square.RIGHT);
				}
				if (square.getFile() - 'a' != m_canPotentiallyCastle[m_color][1] && !king.isValidMove(square))
				{
					canCastle = false;
					break;
				}
			}
			if (canCastle)
			{
				// So far, so good. Make sure the rook can move all
				// the way to the f file.
				square = m_squares[m_canPotentiallyCastle[m_color][1]][kingSquare.getRank() - 1];
				Piece rook = square.getPiece();
				if (rook != null && rook instanceof Rook)
				{
					while (square.getFile() != 'a' + m_nranks - 3)
					{
						if (square.getFile() > 'a' + m_nranks - 3)
						{
							square = square.getNeighbor(Square.LEFT);
						}
						else
						{
							square = square.getNeighbor(Square.RIGHT);
						}
						if (square != kingSquare && square.getPiece() != null)
						{
							canCastle = false;
							break;
						}
					}
				}
			}
			m_canHCastle = canCastle;
		}
	}

	private void updateStatusLabel()
	{
		if (!m_loading)
		{
			m_drawButton.setEnabled(false);
			m_resignButton.setEnabled(false);
			String status = "";
			// We have several statuses here. If the game is concluded,
			// update accordingly. Otherwise, if there is an auth key,
			// this is a live game that can update the database. Indicate
			// whether or not this is your move, your opponent's move, or
			// a game where you are simply spectating.
			if (m_result == BLACKOFFEREDDRAW || m_result == WHITEOFFEREDDRAW)
			{
				m_canACastle = m_canHCastle = false;
				if ((m_result == BLACKOFFEREDDRAW && m_canPlay[Piece.WHITE]) || (m_result == WHITEOFFEREDDRAW && m_canPlay[Piece.BLACK]))
				{
					status = "Draw offered, awaiting acceptance.";
					DOM.setElementProperty(m_drawButton.getElement(), "title", "Accept or Refuse Draw");
					m_drawButton.setEnabled(true);
				}
				else
				{
					status = "Awaiting opponent's acceptance of your draw offer.";
				}
			}
			else if (m_result == ONGOING)
			{
				if (m_canPlay[m_color])
				{
					status = "Awaiting your move.";
					DOM.setElementProperty(m_drawButton.getElement(), "title", "Offer Draw");
					m_drawButton.setEnabled(true);
					m_resignButton.setEnabled(true);
				}
				else if (m_canPlay[1 - m_color])
				{
					status = "Waiting for opponent's move.";
					m_canACastle = m_canHCastle = false;
				}
				else
				{
					status = "Closed game.";
					if (m_color == Piece.WHITE)
					{
						status += " White to move.";
					}
					else
					{
						status += " Black to move.";
					}
					m_canACastle = m_canHCastle = false;
				}
			}
			else if (m_result == WHITEWON)
			{
				status = "White wins.";
			}
			else if (m_result == BLACKWON)
			{
				status = "Black wins.";
			}
			else if (m_result == DRAW)
			{
				status = "Draw.";
			}
			m_statusLabel.setText(status);
			// Update the castle buttons
			m_aCastleButton.setEnabled(m_canACastle);
			m_hCastleButton.setEnabled(m_canHCastle);
		}
	}

	// Posts the turn to the server
	private void doPostTurn(String url, String postData)
	{
		// DEBUG
		// HACK
		// FIXME
		// TODO by ejvos
		m_postData = postData;
		m_spinner.show();
		// ejvos : send message here
		RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
		try
		{
			Request response = builder.sendRequest(postData, new RequestCallback()
			{
				public void onError(Request request, Throwable exception)
				{
					m_spinner.hide();
					new MoveDialog("Your move failed to post to the database.", Board.this).show();
				}

				public void onResponseReceived(Request request, Response response)
				{
					m_spinner.hide();
					switch (response.getStatusCode())
					{
						case 200:
							// success!
							if (m_updateCommand != null)
							{
								m_updateCommand.execute();
								updateGameLogTable(m_updateNotation);
								m_updateCommand = null;
								m_updateNotation = null;
								String newContent = response.getText().trim();
								m_pgn = newContent.substring(32);
								m_postData = null;
							}
							break;
					}
				}
			});
		}
		catch (RequestException e)
		{
			m_spinner.hide();
			new MoveDialog("Your move failed to post to the database for an unknown reason.", Board.this).show();
			m_postData = null;
		}
	}

	// Asks the server for an update on the current board
	private void doCheckUpdate(String url, String postData)
	{
		RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
		try
		{
			Request response = builder.sendRequest(postData, new RequestCallback()
			{
				public void onError(Request request, Throwable exception)
				{
				}

				public void onResponseReceived(Request request, Response response)
				{
					switch (response.getStatusCode())
					{
						case 200:
							// success!
							// 1 or 0: update or not
							String newContent = response.getText().trim();
							if (!newContent.isEmpty())
							{
								newContent = newContent.substring(32);
								m_pgn = newContent;
								int index = newContent.lastIndexOf(']');
								String movelistString = newContent.substring(index + 1);
								movelistString.replaceAll("\\<[^\\>]*\\>", "");
								m_logPanel.remove(m_gameLogTable);
								m_gameLogTable = new GameLogTable(Board.this, m_whiteLastName, m_blackLastName, getMap());
								m_logPanel.add(m_gameLogTable);
								parseMoveList(movelistString);
							}
							break;
					}
				}
			});
		}
		catch (RequestException e)
		{
		}
	}

	public void removePiece(Piece piece)
	{
		m_allPieces.get(piece.getColor()).remove(m_allPieces.get(piece.getColor()).indexOf(piece));
	}

	public void addPiece(Piece piece)
	{
		m_allPieces.get(piece.getColor()).add(piece);
	}

	public final Iterator getColorPiecesIterator(int color)
	{
		return m_allPieces.get(m_color).iterator();
	}

	// Queries whether the King of a given color is under check
	public final boolean isChecked(int color)
	{
		Square square = m_kings[color].getSquare();
		for (Piece piece : m_allPieces.get(1 - color))
		{
			if (piece.threatens(square))
				return true;
		}
		return false;
	}

	// Queries whether any piece of the given color has a valid move
	public final boolean hasValidMove(int color)
	{
		ArrayList<Square> movelist = new ArrayList<Square>();
		for (Piece piece : m_allPieces.get(color))
		{
			piece.getMoveList(movelist);
			if (!movelist.isEmpty())
			{
				return true;
			}
		}
		return false;
	}

	public Piece getSelectedPiece()
	{
		return m_selectedPiece;
	}

	public Piece getLastMovedPiece()
	{
		return m_lastMovedPiece;
	}

	public void setLastMovedPiece(Piece piece)
	{
		m_lastMovedPiece = piece;
	}

	// Returns the width of the board.
	public int getWidth()
	{
		return m_grid.getOffsetWidth();
	}

	// Returns the position of X coordinate of center of the board
	public int getCenterX()
	{
		return m_grid.getAbsoluteLeft() + m_grid.getOffsetWidth() / 2;
	}

	// Returns the position of X coordinate of center of the board
	public int getCenterY()
	{
		return m_grid.getAbsoluteTop() + m_grid.getOffsetHeight() / 2;
	}

	public int getRanks()
	{
		return m_nranks;
	}

	public int getFiles()
	{
		return m_nfiles;
	}

	public void clearCastleFlag(int m_color, int file)
	{
		if (m_canPotentiallyCastle[m_color][0] == file)
		{
			m_canPotentiallyCastle[m_color][0] = -1;
		}
		if (m_canPotentiallyCastle[m_color][1] == file)
		{
			m_canPotentiallyCastle[m_color][1] = -1;
		}
	}

	public void clearBothCastleFlags(int m_color)
	{
		m_canPotentiallyCastle[m_color][0] = m_canPotentiallyCastle[m_color][1] = -1;
	}

	public boolean isCurrent()
	{
		return m_isCurrent;
	}

	public void flip()
	{
		Widget tmp;
		String tmpText;
		for (int rank = 0; rank < m_nranks / 2; ++rank)
		{
			for (int file = 0; file < m_nfiles; ++file)
			{
				tmp = m_grid.getWidget(m_nranks - rank - 1, file);
				m_grid.setWidget(m_nranks - rank - 1, file, m_grid.getWidget(rank, file));
				m_grid.setWidget(rank, file, tmp);
			}
			if (m_leftRankLabelGrid != null)
			{
				tmpText = m_leftRankLabelGrid.getText(m_nranks - rank - 1, 0);
				m_leftRankLabelGrid.setText(m_nranks - rank - 1, 0, m_leftRankLabelGrid.getText(rank, 0));
				m_leftRankLabelGrid.setText(rank, 0, tmpText);
			}
			if (m_rightRankLabelGrid != null)
			{
				tmpText = m_rightRankLabelGrid.getText(m_nranks - rank - 1, 0);
				m_rightRankLabelGrid.setText(m_nranks - rank - 1, 0, m_rightRankLabelGrid.getText(rank, 0));
				m_rightRankLabelGrid.setText(rank, 0, tmpText);
			}
		}
		for (int file = 0; file < m_nfiles / 2; ++file)
		{
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				tmp = m_grid.getWidget(rank, m_nfiles - file - 1);
				m_grid.setWidget(rank, m_nfiles - file - 1, m_grid.getWidget(rank, file));
				m_grid.setWidget(rank, file, tmp);
			}
			if (m_topFileLabelGrid != null)
			{
				tmpText = m_topFileLabelGrid.getText(0, m_nfiles - file);
				m_topFileLabelGrid.setText(0, m_nfiles - file, m_topFileLabelGrid.getText(0, file + 1));
				m_topFileLabelGrid.setText(0, file + 1, tmpText);
			}
			if (m_bottomFileLabelGrid != null)
			{
				tmpText = m_bottomFileLabelGrid.getText(0, m_nfiles - file);
				m_bottomFileLabelGrid.setText(0, m_nfiles - file, m_bottomFileLabelGrid.getText(0, file + 1));
				m_bottomFileLabelGrid.setText(0, file + 1, tmpText);
			}
		}
		tmpText = m_whiteLabel.getText();
		m_whiteLabel.setText(m_blackLabel.getText());
		m_blackLabel.setText(tmpText);
	}

	public String getMap()
	{
		String map = "";
		for (int file = 0; file < m_nfiles; ++file)
		{
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				map += m_squares[file][rank].getMapSymbol();
			}
		}
		return map;
	}

	public void showMap(String notation, String map)
	{
		if (m_selectedPiece != null)
		{
			m_selectedPiece.getSquare().setSelected(false);
			setMoveableSquares(false);
			m_selectedPiece = null;
		}
		int i = 0;
		for (int file = 0; file < m_nfiles; ++file)
		{
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				char c = map.charAt(i);
				m_squares[file][rank].displayMapSymbol(map.charAt(i++));
			}
		}
		m_logPanel.getHeaderTextAccessor().setText(notation);
		m_isCurrent = false;
		m_drawButton.setEnabled(false);
		m_resignButton.setEnabled(false);
		m_aCastleButton.setEnabled(false);
		m_hCastleButton.setEnabled(false);
		m_statusLabel.setText("Browsing game history.");
	}

	public void showCurrentPosition(String notation)
	{
		int i = 0;
		for (int file = 0; file < m_nfiles; ++file)
		{
			for (int rank = 0; rank < m_nranks; ++rank)
			{
				m_squares[file][rank].displayCurrentPiece();
			}
		}
		m_isCurrent = true;
		m_logPanel.getHeaderTextAccessor().setText(notation);
		updateStatusLabel();
	}

	// ////////////////////////////////////////////////

	// / Result of the game
	private int m_result;
	static public final int ONGOING = 0;
	static public final int WHITEWON = 1;
	static public final int BLACKWON = 2;
	static public final int WHITEOFFEREDDRAW = 3;
	static public final int BLACKOFFEREDDRAW = 4;
	static public final int DRAW = 5;

	// Number of ranks on the board
	private int m_nranks;

	// Number of files on the board
	private int m_nfiles;

	// Color of the current player
	private int m_color;

	// Current turn
	private int m_turn;

	// Starting half move to display (-1 if not specified)
	private int m_startPly;

	// Whether the board is still loading the initial PGN
	private boolean m_loading;

	// Currently selected piece
	private Piece m_selectedPiece;

	// The (cached) movelist of the currently selected piece
	private ArrayList<Square> m_selectedMovelist;

	// The piece that was last moved
	private Piece m_lastMovedPiece;

	// Whether the indicated color is allowed to play
	private boolean[] m_canPlay;

	// Player names. First name isn't currently used
	private String m_whiteFirstName, m_whiteLastName;
	private String m_blackFirstName, m_blackLastName;

	// Array of squares for the board
	public Square[][] m_squares;

	// All the pieces in the game
	private ArrayList<ArrayList<Piece>> m_allPieces;

	// The two kings of the game
	private King[] m_kings;

	// Whether each side can potentially castle. The integers stored
	// here are the files of the rooks with which the king can castle,
	// stored in order of increasing file.
	private int[][] m_canPotentiallyCastle;

	// Whether the current color can castle on the a-side
	private boolean m_canACastle;

	// Whether the current color can castle on the h-side
	private boolean m_canHCastle;

	// Whether the board display is current to the last move
	private boolean m_isCurrent;

	// Representation of the game in PGN form
	private String m_pgn;

	// ////////////////////////////////////////////////
	// Database post variables
	// ////////////////////////////////////////////////

	// Userid of the player
	private String m_userid;

	// Copy of the data we attempted to post
	private String m_postData;

	// Deferred command to run upon successful post
	private Command m_updateCommand;

	// Deferred notation to update
	private String m_updateNotation;

	// URL for postmove.php
	private final String m_postmoveURL;

	// ////////////////////////////////////////////////
	// User interface elements and handling
	// ////////////////////////////////////////////////

	// Grid used to layout the squares
	private TightGrid m_grid;

	// Grids used to layout the labels
	private TightGrid m_topFileLabelGrid, m_bottomFileLabelGrid, m_leftRankLabelGrid, m_rightRankLabelGrid;

	// Status label indicating move freedom
	private Label m_statusLabel;

	// Stores game log in algebraic notation
	private GameLogTable m_gameLogTable;

	// Spin panel used to block the UI during post request
	private SpinPanel m_spinner;

	// Button to offer a draw
	private SizedPushButton m_drawButton;

	// Button to resign
	private SizedPushButton m_resignButton;

	// Button for castling
	private SizedPushButton m_aCastleButton;
	private SizedPushButton m_hCastleButton;

	// Button to flip board
	private SizedPushButton m_flipButton;

	// Game navigation buttons
	private SizedPushButton m_startButton;
	private SizedPushButton m_rewindButton;
	private SizedPushButton m_forwardButton;
	private SizedPushButton m_endButton;

	// Disclosure panel showing the last move and hiding the gamelog
	private DisclosurePanel m_logPanel;

	// Event information and label
	private String m_eventString;
	private String m_siteString;
	private String m_dateString;
	private String m_roundString;
	private Label m_eventLabel;
	private Label m_siteLabel;

	// Labels for each side
	private Label m_blackLabel;
	private Label m_whiteLabel;
}
